package com.atguigu.gmall.index.service;

import com.atguigu.gmall.common.bean.ResponseVo;
import com.atguigu.gmall.index.aspect.GmallCache;
import com.atguigu.gmall.index.feign.GmallPmsClient;
import com.atguigu.gmall.index.tools.DistributedLock;
import com.atguigu.gmall.pms.api.GmallPmsApi;
import com.atguigu.gmall.pms.entity.CategoryEntity;
import com.baomidou.mybatisplus.annotation.TableId;
import io.prometheus.client.Summary;
import io.swagger.models.auth.In;
import org.apache.commons.lang.StringUtils;
import org.redisson.api.RCountDownLatch;
import org.redisson.api.RLock;
import org.redisson.api.RReadWriteLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import sun.rmi.runtime.Log;

import java.awt.font.TextMeasurer;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @author Jia Fa
 * @create 2021-12-09 16:51
 */
@Service
public class IndexService {

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private DistributedLock distributedLock;

    @Autowired
    private RedissonClient redissonClient;   // 可提供红锁适用于多线程 集成与Redis

    @Autowired
    GmallPmsClient pmsClient;

    public List<CategoryEntity> queryCategoriesById() {
        // 查询一级分类到页面
        ResponseVo<List<CategoryEntity>> listResponseVo = this.pmsClient.categoryByPid(0L);
        List<CategoryEntity> categoryEntities = listResponseVo.getData();
        return categoryEntities;
    }

    public void testLock() {

        RLock lock = this.redissonClient.getLock("lock");
        lock.lock();

        try {
            // 获取到锁,执行业务方法
            // 查询Redis中的num 值 再执行++ 操作
            String num = this.redisTemplate.opsForValue().get("num");
            if (StringUtils.isBlank(num)) {
                // 如果不存在,则直接返回
                return;
            }
            // 存在则转换为int 在执行_++_ 操作
            int n = Integer.parseInt(num);

            this.redisTemplate.opsForValue().set("num", String.valueOf(++n));

            try {
                // 默认有一个自动续期程序
                Thread.sleep(60000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        } finally {

            lock.unlock();
        }


    }

    /**
     * 缓存分布式锁机制测试代码
     */
    public void testLock2() {
        // 从Redis中获取到锁, setNX方法=======================setNx 此方法仅可设置一次 符合独占排他
        // 由于线程时间执行不确定 过期后其他线程进入,原线程执行完后删除锁删除的是其他线程的 造成锁误删 解决 给每个线程的锁加上唯一标识UUID
        String uuid = UUID.randomUUID().toString();
        // 设置锁与配置过期时间应当具备原子性,一同设置  设置锁过期时间防止线程获取到锁立即宕机导致死锁
        Boolean lock = this.redisTemplate.opsForValue().setIfAbsent("lock", uuid, 3, TimeUnit.SECONDS);

        if (lock) {
            // 获取到锁,执行业务方法
            // 查询Redis中的num 值 再执行++ 操作
            String num = this.redisTemplate.opsForValue().get("num");
            if (StringUtils.isBlank(num)) {
                // 如果不存在,则直接返回
                return;
            }
            // 存在则转换为int 在执行_++_ 操作
            int n = Integer.parseInt(num);

            this.redisTemplate.opsForValue().set("num", String.valueOf(++n));

            // 完成修改后释放锁=================
            // 删除锁之前判断是否是自己的锁    注意  删除锁与判断UUID是否相等也应当具备原子性(高并发)
            // 使用lua 脚本将查询锁与删除锁连成一个指令 构成原子性操作 不可打断插入
            String script = "if redis.call('get',KEYS[1]) == ARGV[1] " +
                    "then " +
                    "   return redis.call('del',KEYS[1]) " +
                    "else " +
                    "   return 0 " +
                    "end";
            this.redisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class), Arrays.asList("lock"), uuid);
            /*if (StringUtils.equals(uuid, this.redisTemplate.opsForValue().get("num"))) {
                this.redisTemplate.delete("lock");
            }*/
        } else {
            try {
                // 睡一段时间再重新尝试获取锁
                Thread.sleep(1000);
                // ---------------------
                testLock();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }


    }

    // 可重入锁判断A方法  A调用B
    public void testLock3() {

        String uuid = UUID.randomUUID().toString();
        // 获取到锁,执行业务方法
        this.distributedLock.tryLock("lock", uuid, 30);
        // 查询Redis中的num 值 再执行++ 操作
        String num = this.redisTemplate.opsForValue().get("num");
        if (StringUtils.isBlank(num)) {
            // 如果不存在,则直接返回
            return;
        }
        // 存在则转换为int 在执行_++_ 操作
        int n = Integer.parseInt(num);

        // 假如任务需要90 s 才能完成 期间定时器会进行自动续期
        try {
            TimeUnit.SECONDS.sleep(90);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        this.redisTemplate.opsForValue().set("num", String.valueOf(++n));
        // ==================重入======================
        this.testSubLock(uuid);

        // 解锁
        this.distributedLock.unLock("lock", uuid);

    }

    // 测试可重入锁的B方法
    public void testSubLock(String uuid) {
        this.distributedLock.tryLock("lock", uuid, 30);
        // 别输出了,太慢了
//        System.out.println("==========B 方法执行=============");

        this.distributedLock.unLock("lock", uuid);
    }

    //读锁  锁需要一致
    public void readLock() {
        RReadWriteLock rwLock = this.redissonClient.getReadWriteLock("RWLock");
        System.out.println("读锁执行");
        rwLock.readLock().lock(10, TimeUnit.SECONDS); // 10s 自动过期
    }

    //写锁  锁需要一致
    public void writeLock() {
        RReadWriteLock rwLock = this.redissonClient.getReadWriteLock("RWLock");
        System.out.println("写锁执行");
        rwLock.writeLock().lock(10, TimeUnit.SECONDS);// 10s 自动过期
    }

    //锁门
    public void latchLock() {
        RCountDownLatch countLock = this.redissonClient.getCountDownLatch("CountLock");

        countLock.trySetCount(5); // 设置资源量的值 计数位
        try {
            countLock.await();  // 注意 是await 方法
            System.out.println("锁门了");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    //出来1人
    public void countDown() {
        RCountDownLatch countLock = this.redissonClient.getCountDownLatch("CountLock");
        System.out.println("出来1位同学....");
        countLock.countDown();  // 计数位-1
    }


    @GmallCache(prefix = "index:cates:", timeout = 14400, random = 3600, lock = "lock:")
    public List<CategoryEntity> queryCategoriesWithSubsByPid(Long pid) {
        ResponseVo<List<CategoryEntity>> categories = this.pmsClient.categoriesWithSubsByPid(pid);
        List<CategoryEntity> categoryEntities = categories.getData();
        return categoryEntities;
    }
}
